/**
 * Captain
 * © 2017 Captain copyright，All rights reserved.
 * http://www.sccaptain.com.cn
 * 
 * JAVA : 8
 * 文  件  名: SpringDataAnnotationUtils.java
 * 创  建  人: Roc Zheng (roc.djz@gmail.com)
 * 创建时间: 2017年7月6日 上午10:14:06
 * 版         本: 1.0.0
 * 备         注:
 * 修订历史:
 */
package com.material.c2c.web.util;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.Set;

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.MethodParameter;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.data.domain.Pageable;
import org.springframework.util.ObjectUtils;

/**
 * 
 * 
 * @since 1.0
 * @version 1.0
 * @author Roc Zheng (roc.djz@gmail.com)
 */
public abstract class SpringDataAnnotationUtils {

  private SpringDataAnnotationUtils() {}

  /**
   * Asserts uniqueness of all {@link Pageable} parameters of the method of the given {@link MethodParameter}.
   * 
   * @param parameter must not be {@literal null}.
   */
  public static void assertPageableUniqueness(MethodParameter parameter) {
    Method method = parameter.getMethod();
    if (containsMoreThanOnePageableParameter(method)) {
      Annotation[][] annotations = method.getParameterAnnotations();
      assertQualifiersFor(method.getParameterTypes(), annotations);
    }
  }

  /**
   * Returns whether the given {@link Method} has more than one {@link Pageable} parameter.
   * 
   * @param method must not be {@literal null}.
   * @return
   */
  private static boolean containsMoreThanOnePageableParameter(Method method) {
    boolean pageableFound = false;
    for (Class<?> type : method.getParameterTypes()) {
      if (pageableFound && type.equals(Pageable.class)) {
        return true;
      }

      if (type.equals(Pageable.class)) {
        pageableFound = true;
      }
    }

    return false;
  }

  /**
   * Returns the value of the given specific property of the given annotation. If the value of that property is the
   * properties default, we fall back to the value of the {@code value} attribute.
   * 
   * @param annotation must not be {@literal null}.
   * @param property must not be {@literal null} or empty.
   * @return
   */
  @SuppressWarnings("unchecked")
  public static <T> T getSpecificPropertyOrDefaultFromValue(Annotation annotation, String property) {

    Object propertyDefaultValue = AnnotationUtils.getDefaultValue(annotation, property);
    Object propertyValue = AnnotationUtils.getValue(annotation, property);

    return (T) (ObjectUtils.nullSafeEquals(propertyDefaultValue, propertyValue) ? AnnotationUtils.getValue(annotation)
        : propertyValue);
  }

  /**
   * Asserts that every {@link Pageable} parameter of the given parameters carries an {@link Qualifier} annotation to
   * distinguish them from each other.
   * 
   * @param parameterTypes must not be {@literal null}.
   * @param annotations must not be {@literal null}.
   */
  public static void assertQualifiersFor(Class<?>[] parameterTypes, Annotation[][] annotations) {

    Set<String> values = new HashSet<String>();

    for (int i = 0; i < annotations.length; i++) {

      if (Pageable.class.equals(parameterTypes[i])) {

        Qualifier qualifier = findAnnotation(annotations[i]);

        if (null == qualifier) {
          throw new IllegalStateException(
              "Ambiguous Pageable arguments in handler method. If you use multiple parameters of type Pageable you need to qualify them with @Qualifier");
        }

        if (values.contains(qualifier.value())) {
          throw new IllegalStateException("Values of the user Qualifiers must be unique!");
        }

        values.add(qualifier.value());
      }
    }
  }

  /**
   * Returns a {@link Qualifier} annotation from the given array of {@link Annotation}s. Returns {@literal null} if the
   * array does not contain a {@link Qualifier} annotation.
   * 
   * @param annotations must not be {@literal null}.
   * @return
   */
  public static Qualifier findAnnotation(Annotation[] annotations) {

    for (Annotation annotation : annotations) {
      if (annotation instanceof Qualifier) {
        return (Qualifier) annotation;
      }
    }

    return null;
  }
}
